﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Input;

namespace Project1
{
    public class Camera
    {

        #region Member Variables
        /// <summary>
        /// Camera Graphics Device Manager
        /// </summary>
        private GraphicsDeviceManager mGraphics;
        /// <summary>
        /// Camera Eye position in space
        /// </summary>
        private Vector3 mEye = new Vector3( 1000, 1000, 1000 );
        /// <summary>
        /// THe location the camera is looking in space
        /// </summary>
        //private Vector3 mCenter = new Vector3( -1000, 200, 0 );
        private Vector3 mCenter = new Vector3(0, 0, 0);
        /// <summary>
        /// Camera Up direction
        /// </summary>
        private Vector3 mUp = new Vector3(0, 1, 0);
        /// <summary>
        /// Camera Field of View (radians)
        /// </summary>
        private float mFov = MathHelper.ToRadians(35);
        /// <summary>
        /// Camera Near Clipping Plane
        /// </summary>
        private float mZnear = 10;
        /// <summary>
        /// Camera Far Clipping Plane
        /// </summary>
        private float mZfar = 20000;

        /// <summary>
        /// Camera View Matrix
        /// </summary>
        private Matrix mView;
        /// <summary>
        /// Camera Projection Matrix
        /// </summary>
        private Matrix mProjection;

        /// <summary>
        /// Camera mouse Pitch Control Boolean
        /// </summary>
        private bool mMousePitchYaw = true;
        /// <summary>
        /// Camera Tilt Control Boolean
        /// </summary>
        private bool mMousePanTilt = true;
        /// <summary>
        /// The current state of the mouse
        /// </summary>
        //private MouseState mLastMouseState;

        /// <summary>
        /// Whether or not we are using a chase camera
        /// </summary>
        private bool mUseChaseCamera = false;

        /// <summary>
        /// The desired eye of the camera
        /// </summary>
        private Vector3 mDesiredEye = Vector3.Zero;

        /// <summary>
        /// The velocity of the camera
        /// </summary>
        private Vector3 mVelocity = Vector3.Zero;

        /// <summary>
        /// The stiffness of the camera spring
        /// </summary>
        private float mStiffness = 100;

        /// <summary>
        /// The damping of the camera spring
        /// </summary>
        private float mDamping = 60;

        /// <summary>
        /// The desired up vector for our camera
        /// </summary>
        private Vector3 mDesiredUp = Vector3.Zero;
        
        #endregion

        #region Properties
        /// <summary>
        /// Sets or returns the position of the cameras eye
        /// </summary>
        public Vector3 Eye { get { return mEye; } set { mEye = value; ComputeView(); } }
        /// <summary>
        /// Sets or returns the cameras center
        /// </summary>
        public Vector3 Center { get { return mCenter; } set { mCenter = value; ComputeView(); } }
        /// <summary>
        /// Sets or returns the cameras up vector
        /// </summary>
        public Vector3 Up { get { return mUp; } set { mUp = value; ComputeView(); } }
        /// <summary>
        /// Sets or returns the cameras Field of View
        /// </summary>
        public float Fov { get { return mFov; } set { mFov = value; ComputeProjection(); } }
        /// <summary>
        /// Sets or returns the cameras znear
        /// </summary>
        public float Znear { get { return mZnear; } set { mZnear = value; ComputeProjection(); } }
        /// <summary>
        /// Sets or returns the cameras zfar
        /// </summary>
        public float Zfar { get { return mZfar; } set { mZfar = value; ComputeProjection(); } }
        /// <summary>
        /// Returns the Camera View Matrix
        /// </summary>
        public Matrix View { get { return mView; } }
        /// <summary>
        /// Returns the Camera Projection matrix
        /// </summary>
        public Matrix Projection { get { return mProjection; } }
        /// <summary>
        /// Gets or sets the boolean which controls the users ability to control pitch yaw with the mouse
        /// </summary>
        public bool MoustPitchYaw { get { return mMousePitchYaw; } set { mMousePitchYaw = value; } }
        /// <summary>
        ///  Gets or sets the boolean which controls the users ability to control pan tilt with the mouse
        /// </summary>
        public bool MousePanTilt { get { return mMousePanTilt; } set { mMousePanTilt = value; } }
        /// <summary>
        /// Set or returns whether the camera is a chase camera or not
        /// </summary>
        public bool UseChaseCamera { get { return mUseChaseCamera; } set { mUseChaseCamera = value; } }

        /// <summary>
        /// Sets or returns the cameras desired eye
        /// </summary>
        public Vector3 DesiredEye { get { return mDesiredEye; } set { mDesiredEye = value; } }

        /// <summary>
        /// Sets or returns the camera spring stiffness
        /// </summary>
        public float Stiffness { get { return mStiffness; } set { mStiffness = value; } }

        /// <summary>
        /// Sets or returns the camera spring damping
        /// </summary>
        public float Damping { get { return mDamping; } set { mDamping = value; } }

        public Vector3 DesiredUp { get { return mDesiredUp; } set { mDesiredUp = value; } }
        #endregion

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="aGraphics"></param>
        public Camera(GraphicsDeviceManager aGraphics)
        {
            this.mGraphics = aGraphics;
        }

        /// <summary>
        /// Computes the View Matrix
        /// </summary>
        private void ComputeView()
        {
            mView = Matrix.CreateLookAt(mEye, mCenter, mUp);
        }

        /// <summary>
        /// Computes the Projection Matrix
        /// </summary>
        private void ComputeProjection()
        {
            mProjection = Matrix.CreatePerspectiveFieldOfView(mFov,
                mGraphics.GraphicsDevice.Viewport.AspectRatio, mZnear, mZfar);
        }

        /// <summary>
        /// Camera Initialization
        /// </summary>
        public void Initialize()
        {
            ComputeView();
            ComputeProjection();
        }
        
        /// <summary>
        /// Updates the camera based on runtime activities
        /// </summary>
        /// <param name="aGameTime"></param>
        public void Update(GameTime aGameTime)
        {
            #region Mouse Controls
                /*MouseState lMouseState = Mouse.GetState();

                if (mMousePitchYaw && lMouseState.LeftButton == ButtonState.Pressed &&
                    mLastMouseState.LeftButton == ButtonState.Pressed)
                {
                    float lChangeY = lMouseState.Y - mLastMouseState.Y;
                    Pitch(-lChangeY * 0.005f);

                    float lChangeX = lMouseState.X - mLastMouseState.X;
                    Yaw(lChangeX * 0.005f);
                }

                if (mMousePanTilt && lMouseState.RightButton == ButtonState.Pressed &&
                    mLastMouseState.RightButton == ButtonState.Pressed)
                {
                    float changeY = lMouseState.Y - mLastMouseState.Y;
                    Tilt(-changeY * 0.005f);
                    float changeX = lMouseState.X - mLastMouseState.X;
                    Pan(-changeX * 0.005f);
                    ComputeView();
                }

                if (mUseChaseCamera)
                {
                    // Calculate the spring force
                    Vector3 lStrech = mDesiredEye - mEye;
                    Vector3 lStrech2 = mDesiredUp - mUp;

                    Vector3 lAcceleration = lStrech * mStiffness - mVelocity * mDamping;
                    Vector3 lAcceleration2 = lStrech2 * mStiffness - mUp * mDamping;
                    // Apply acceleration
                    mVelocity += lAcceleration * (float)aGameTime.ElapsedGameTime.TotalSeconds;

                    mUp += lAcceleration2 * (float)aGameTime.ElapsedGameTime.TotalSeconds * (float)aGameTime.ElapsedGameTime.TotalSeconds;
                    //mUp.Normalize();

                    // Apply velocity
                    mEye += mVelocity * (float)aGameTime.ElapsedGameTime.TotalSeconds;
                }
                mLastMouseState = lMouseState;*/
            #endregion
        }

        /// <summary>
        /// The rotation of the camera object around the object and the camera X axis
        /// </summary>
        /// <param name="aAngle"></param>
        public void Pitch(float aAngle)
        {
            // Need a vector in the camera X direction
            Vector3 lCameraZ = mEye - mCenter;
            Vector3 lCameraX = Vector3.Cross(mUp, lCameraZ);
            float lLen2 = lCameraX.LengthSquared();
            if (lLen2 > 0)
                lCameraX.Normalize();
            else
                lCameraX = new Vector3(1, 0, 0);

            Matrix t1 = Matrix.CreateTranslation(-mCenter);
            Matrix r = Matrix.CreateFromAxisAngle(lCameraX, aAngle);
            Matrix t2 = Matrix.CreateTranslation(mCenter);

            Matrix M = t1 * r * t2;
            mEye = Vector3.Transform(mEye, M);
            ComputeView();
        }

        public void Yaw(float aAngle)
        {
            // Need a vector in the camera X direction
            Vector3 lCameraZ = mEye - mCenter;
            Vector3 lCameraX = Vector3.Cross(mUp, lCameraZ);
            Vector3 lCameraY = Vector3.Cross(lCameraZ, lCameraX);
            float lLen2 = lCameraY.LengthSquared();
            if (lLen2 > 0)
                lCameraY.Normalize();
            else 
                lCameraY = new Vector3( 0, 1, 0 );
            
            Matrix t1 = Matrix.CreateTranslation(-mCenter);
            Matrix r = Matrix.CreateFromAxisAngle(lCameraY, aAngle);
            Matrix t2 = Matrix.CreateTranslation(mCenter);
            
            Matrix M = t1 * r * t2;
            mEye = Vector3.Transform(mEye, M);
            ComputeView();
        }
        public void Pan(float aAngle)
        {
            Vector3 lCameraZ = mEye - mCenter;
            Vector3 lCameraX = Vector3.Cross(mUp, lCameraZ);
            Vector3 lCameraY = Vector3.Cross(lCameraZ, lCameraX);
            float lLen2 = lCameraX.LengthSquared();
            if (lLen2 > 0)
                lCameraY.Normalize();
            else
                lCameraY = new Vector3(0, 1, 0);

            // Rotate center around the eye
            Matrix t1 = Matrix.CreateTranslation(-mEye);
            Matrix r = Matrix.CreateFromAxisAngle(lCameraY, aAngle);
            Matrix t2 = Matrix.CreateTranslation(mEye);

            Matrix M = t1 * r * t2;
            mCenter = Vector3.Transform(mCenter, M);
            
        }
        public void Tilt(float aAngle)
        {
            Vector3 lCameraZ = mEye - mCenter;
            Vector3 lCameraX = Vector3.Cross(mUp, lCameraZ);
            float lLen2 = lCameraX.LengthSquared();
            if (lLen2 > 0)
            {
                lCameraX.Normalize();
            }
            else
            {
                lCameraX = new Vector3(1, 0, 0);
            }

            Matrix t1 = Matrix.CreateTranslation(-mEye);
            Matrix r = Matrix.CreateFromAxisAngle(lCameraX, aAngle);
            Matrix t2 = Matrix.CreateTranslation(mEye);
            Matrix M = t1 * r * t2;
            mCenter = Vector3.Transform(mCenter, M);
        }

    }// End Class Camera
}// End Namespace TurretGame
